/*
 * jxUtils Project (2013, 2014)
 * 
 * by Denilson Edinaldo Pinto
 * 
 * LGPL
 * Copyright (C) 2014
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.jxUtils.collection;

import java.io.File;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.jxUtils.TypeUtil;
import org.jxUtils.check.CheckUtil;
import org.jxUtils.lang.StringAsync;

/**
 * @author Denilson Edinaldo Pinto
 * @project jxUtils
 * @package org.jxUtils.collection
 * @date 07/02/2013 - 21:09:51
 */
public final class CollectionUtil {
	
	/**
	 * Retorna uma nova instância (clone) da {@code Collection<T>}
	 * 
	 * @param list
	 * @return cloned list
	 */
	public static <T> List<T> clone(final Collection<T> list) {
	
		List<T> dest = null;
		
		//
		if(CheckUtil.isNotNull(list)) {
			
			//
			if(list.isEmpty()) {
				
				return Collections.emptyList();
			}
			
			dest = new ArrayList<T>(list.size());
			
			//
			for(int index = 0; index < list.size(); index++) {
				
				dest.add(null);
			}
			
			Collections.copy(dest, new ArrayList<>(list));
		}
		
		return dest;
	}
	
	/**
	 * Copia os elementos do <b>array</b> para um novo Array utilizando o método {@link Arrays#copyOf(boolean[], int)}
	 * 
	 * @param array
	 * @return array copy
	 */
	public static boolean[] copy(final boolean[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			return Arrays.copyOf(array, array.length);
		}
		
		return null;
	}
	
	/**
	 * Copia os elementos do <b>array</b> para um novo Array utilizando o método {@link Arrays#copyOf(byte[], int)}
	 * 
	 * @param array
	 * @return array copy
	 */
	public static byte[] copy(final byte[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			return Arrays.copyOf(array, array.length);
		}
		
		return null;
	}
	
	/**
	 * Copia os elementos do <b>array</b> para um novo Array utilizando o método {@link Arrays#copyOf(char[], int)}
	 * 
	 * @param array
	 * @return array copy
	 */
	public static char[] copy(final char[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			return Arrays.copyOf(array, array.length);
		}
		
		return null;
	}
	
	/**
	 * Copia os elementos do <b>array</b> para um novo Array utilizando o método {@link Arrays#copyOf(double[], int)}
	 * 
	 * @param array
	 * @return array copy
	 */
	public static double[] copy(final double[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			return Arrays.copyOf(array, array.length);
		}
		
		return null;
	}
	
	/**
	 * Copia os elementos do <b>array</b> para um novo Array utilizando o método {@link Arrays#copyOf(float[], int)}
	 * 
	 * @param array
	 * @return array copy
	 */
	public static float[] copy(final float[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			return Arrays.copyOf(array, array.length);
		}
		
		return null;
	}
	
	/**
	 * Copia os elementos do <b>array</b> para um novo Array utilizando o método {@link Arrays#copyOf(int[], int)}
	 * 
	 * @param array
	 * @return array copy
	 */
	public static int[] copy(final int[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			return Arrays.copyOf(array, array.length);
		}
		
		return null;
	}
	
	/**
	 * Copia os elementos do <b>srcList</b> para o <b>destList</b>
	 * 
	 * @param srcList
	 * @return srcList copy
	 */
	public static <T> List<T> copy(final List<T> srcList) {
	
		ArrayList<T> destList = null;
		
		//
		if(CheckUtil.isNotNull(srcList)) {
			
			destList = new ArrayList<T>(srcList.size());
			
			copy(srcList, destList);
		}
		
		return destList;
	}
	
	/**
	 * Copia os elementos do <b>srcList</b> para o <b>destList</b> utilizando o método {@link Collections#copy(List, List)}
	 * 
	 * @param srcList
	 * @param destList
	 */
	public static <T> void copy(final List<T> srcList, final List<T> destList) {
	
		//
		if(CheckUtil.isNotNullAndNotEmpty(srcList) && CheckUtil.isNotNullAndNotEmpty(destList)) {
			
			Collections.copy(destList, srcList);
		}
	}
	
	/**
	 * Copia os elementos do <b>array</b> para um novo Array utilizando o método {@link Arrays#copyOf(long[], int)}
	 * 
	 * @param array
	 * @return array copy
	 */
	public static long[] copy(final long[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			return Arrays.copyOf(array, array.length);
		}
		
		return null;
	}
	
	/**
	 * Copia os elementos do <b>array</b> para um novo Array utilizando o método {@link Arrays#copyOf(short[], int)}
	 * 
	 * @param array
	 * @return array copy
	 */
	public static short[] copy(final short[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			return Arrays.copyOf(array, array.length);
		}
		
		return null;
	}
	
	/**
	 * Copia os elementos do <b>array</b> para um novo Array utilizando o método {@link Arrays#copyOf(T[], int)}
	 * 
	 * @param array
	 * @return array copy
	 */
	public static <T> T[] copy(final T[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			return Arrays.copyOf(array, array.length);
		}
		
		return null;
	}
	
	/**
	 * Retorna o <b>index</b> do <b>element</b> na <b>list</b> utilizando o método {@link Collections#binarySearch(List, Object)}
	 * 
	 * @param list
	 * @param element
	 * @return index
	 */
	public static <T extends Comparable<? super T>> int getIndex(final List<T> list, final T element) {
	
		//
		if(CheckUtil.isNotNull(list) && CheckUtil.isNotNull(element)) {
			
			return Collections.binarySearch(list, element);
		}
		
		return -1;
	}
	
	/**
	 * Ordena o <b>array</b> de <b>T</b> utilizando o método {@link SortUtil#heapSort(Comparable[])}
	 * 
	 * @param array
	 */
	public static <T extends Comparable<? super T>> void heapSort(final T[] array) {
	
		SortUtil.heapSort(array);
	}
	
	/**
	 * @param <T>
	 * @param clazz
	 * @return comparator or null
	 */
	public static <T> Comparator<T> makePrimitiveComparator() {
	
		return new Comparator<T>() {
			
			@Override
			public int compare(final T object1, final T object2) {
			
				//
				if(object1 != null && object2 != null) {
					
					//
					if(object1.getClass().equals(BigDecimal.class)) {
						
						return ((BigDecimal)object1).compareTo((BigDecimal)object2);
						
					}else if(object1.getClass().equals(BigInteger.class)) {
						
						return ((BigInteger)object1).compareTo((BigInteger)object2);
						
					}else if(object1.getClass().equals(Boolean.class)) {
						
						return ((Boolean)object1).compareTo((Boolean)object2);
						
					}else if(object1.getClass().equals(Byte.class)) {
						
						return ((Byte)object1).compareTo((Byte)object2);
						
					}else if(object1.getClass().equals(Character.class)) {
						
						return ((Character)object1).compareTo((Character)object2);
						
					}else if(object1.getClass().equals(Date.class)) {
						
						return ((Date)object1).compareTo((Date)object2);
						
					}else if(object1.getClass().equals(Double.class)) {
						
						return ((Double)object1).compareTo((Double)object2);
						
					}else if(object1.getClass().equals(Float.class)) {
						
						return ((Float)object1).compareTo((Float)object2);
						
					}else if(object1.getClass().equals(Integer.class)) {
						
						return ((Integer)object1).compareTo((Integer)object2);
						
					}else if(object1.getClass().equals(Long.class)) {
						
						return ((Long)object1).compareTo((Long)object2);
						
					}else if(object1.getClass().equals(Short.class)) {
						
						return ((Short)object1).compareTo((Short)object2);
						
					}else if(object1.getClass().equals(String.class)) {
						
						return ((String)object1).compareTo((String)object2);
						
					}else if(object1.getClass().equals(URL.class)) {
						
						return ((URL)object1).toString().compareTo(((URL)object2).toString());
					}
				}
				
				return 0;
			}
		};
	}
	
	/**
	 * Obtém o maior valor da <b>list</b> utilizando o método {@link Collections#max(Collection)}
	 * 
	 * @param list
	 * @return max value or null
	 */
	public static <T extends Comparable<? super T>> T maxValue(final List<T> list) {
	
		//
		if(CheckUtil.isNotNullAndNotEmpty(list)) {
			
			return Collections.max(list);
		}
		
		return null;
	}
	
	/**
	 * Obtém o menor valor da <b>list</b> utilizando o método {@link Collections#min(Collection)}
	 * 
	 * @param list
	 * @return min value or null
	 */
	public static <T extends Comparable<? super T>> T minValue(final List<T> list) {
	
		//
		if(CheckUtil.isNotNullAndNotEmpty(list)) {
			
			return Collections.min(list);
		}
		
		return null;
	}
	
	/**
	 * Retorna uma Lista sem os elementos que estão duplicados na Lista <b>list</b>
	 * 
	 * @param list
	 * @return list without duplicate values
	 */
	public static <T> List<T> removeDuplicate(final List<T> list) {
	
		//
		if(CheckUtil.isNotNull(list)) {
			
			//
			if(list.isEmpty()) {
				
				return Collections.emptyList();
			}
			
			return new ArrayList<T>(new HashSet<T>(list));
		}
		
		return null;
	}
	
	/**
	 * Substitui todos os <b>oldValue</b> pelo <b>newValue</b> existentes no <b>list</b>
	 * 
	 * @param list
	 * @param oldValue
	 * @param newValue
	 */
	public static <T> void replaceAll(final List<T> list, final T oldValue, final T newValue) {
	
		//
		if(CheckUtil.isNotNull(list)) {
			
			Collections.replaceAll(list, oldValue, newValue);
		}
	}
	
	/**
	 * Embaralha os elementos do <b>list</b>
	 * 
	 * @param list
	 */
	public static <T> void shuffle(final List<T> list) {
	
		//
		if(CheckUtil.isNotNull(list)) {
			
			Collections.shuffle(list);
		}
	}
	
	/**
	 * Ordena o <b>array</b>
	 * 
	 * @param array
	 */
	public static void sort(final byte[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			Arrays.sort(array);
		}
	}
	
	/**
	 * Ordena o <b>array</b>
	 * 
	 * @param array
	 */
	public static void sort(final char[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			Arrays.sort(array);
		}
	}
	
	/**
	 * Ordena o <b>array</b>
	 * 
	 * @param array
	 */
	public static void sort(final double[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			Arrays.sort(array);
		}
	}
	
	/**
	 * Ordena o <b>array</b>
	 * 
	 * @param array
	 */
	public static void sort(final float[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			Arrays.sort(array);
		}
	}
	
	/**
	 * Ordena o <b>array</b>
	 * 
	 * @param array
	 */
	public static void sort(final int[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			Arrays.sort(array);
		}
	}
	
	/**
	 * Ordena o <b>list</b> utilizando o método {@link Collections#sort(List)}
	 * 
	 * @param list
	 */
	public static <T extends Comparable<? super T>> void sort(final Collection<T> list) {
	
		//
		if(CheckUtil.isNotNull(list)) {
			
			Collections.sort(list instanceof List ? (List<T>)list : new ArrayList<T>(list));
		}
	}
	
	/**
	 * Ordena o <b>array</b>
	 * 
	 * @param array
	 */
	public static void sort(final long[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			Arrays.sort(array);
		}
	}
	
	/**
	 * Ordena o <b>array</b> utilizando o método {@link Arrays#sort(Object[])}
	 * 
	 * @param array
	 */
	public static void sort(final Object[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			Arrays.sort(array);
		}
	}
	
	/**
	 * Ordena o <b>array</b>
	 * 
	 * @param array
	 */
	public static void sort(final short[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			Arrays.sort(array);
		}
	}
	
	/**
	 * Ordena o <b>array</b> de acordo com a Data de Modificação
	 * 
	 * @param files
	 */
	public static void sortByLastModified(final File[] files) {
	
		//
		if(CheckUtil.isNotNull(files)) {
			
			File aux;
			
			//
			for(int i = files.length; i >= 1; i--) {
				
				//
				for(int j = 1; j < i; j++) {
					
					//
					if(files[j - 1].lastModified() > files[j].lastModified()) {
						
						aux = files[j];
						
						files[j] = files[j - 1];
						
						files[j - 1] = aux;
					}
				}
			}
		}
	}
	
	/**
	 * Swaps the elements at the specified positions in the specified list. (If the specified positions are equal, invoking this method leaves the list unchanged.)
	 * 
	 * @param list
	 * @param index1
	 * @param index2
	 */
	public static <T> void swap(final List<T> list, final int index1, final int index2) {
	
		//
		if(CheckUtil.isNotNull(list)) {
			
			Collections.swap(list, index1, index2);
		}
	}
	
	/**
	 * Converte o <b>list</b> em um {@link Enumeration}
	 * 
	 * @param list
	 * @return {@link Enumeration}
	 */
	public static <T> Enumeration<T> toEnum(final List<T> list) {
	
		//
		if(CheckUtil.isNotNull(list)) {
			
			return Collections.enumeration(list);
		}
		
		return null;
	}
	
	/**
	 * Converte o <b>enumeration</b> para um {@link List}.<br>
	 * <br>
	 * Obs.: <b>T</b> não pode ser um tipo primitivo.<br>
	 * <br>
	 * Exemplo: Se quiser converter <b>long[]</b> para <b>List<<long>long></b>, então utilize
	 * <b>Long[]</b> para obter <b>List<<Long>Long></b>.<br>
	 * 
	 * @param array
	 * @return {@link ArrayList}
	 */
	public static <T> List<T> toList(final Enumeration<T> enumeration) {
	
		//
		if(CheckUtil.isNotNull(enumeration)) {
			
			return Collections.list(enumeration);
		}
		
		return null;
	}
	
	/**
	 * Converte {@link Collection}, Primitive Array or Object Array para {@link List}
	 * 
	 * @param value
	 * @return {@link ArrayList}
	 */
	public static List<?> toList(final Object value) {
	
		//
		if(CheckUtil.isNotNull(value)) {
			
			//
			if(TypeUtil.isCollection(value)) {
				
				return new ArrayList<>((Collection<?>)value);
				
			}else if(TypeUtil.isPrimitiveArray(value)) {
				
				final Class<?> type = value.getClass().getComponentType();
				
				//
				if(type.equals(boolean.class)) {
					
					final List<Boolean> list = new ArrayList<>();
					
					//
					for(final boolean item : (boolean[])value) {
						
						list.add(item);
					}
					
					return list;
					
				}else if(type.equals(byte.class)) {
					
					final List<Byte> list = new ArrayList<>();
					
					//
					for(final byte item : (byte[])value) {
						
						list.add(item);
					}
					
					return list;
					
				}else if(type.equals(char.class)) {
					
					final List<Character> list = new ArrayList<>();
					
					//
					for(final char item : (char[])value) {
						
						list.add(item);
					}
					
					return list;
					
				}else if(type.equals(short.class)) {
					
					final List<Short> list = new ArrayList<>();
					
					//
					for(final short item : (short[])value) {
						
						list.add(item);
					}
					
					return list;
					
				}else if(type.equals(int.class)) {
					
					final List<Integer> list = new ArrayList<>();
					
					//
					for(final int item : (int[])value) {
						
						list.add(item);
					}
					
					return list;
					
				}else if(type.equals(long.class)) {
					
					final List<Long> list = new ArrayList<>();
					
					//
					for(final long item : (long[])value) {
						
						list.add(item);
					}
					
					return list;
					
				}else if(type.equals(float.class)) {
					
					final List<Float> list = new ArrayList<>();
					
					//
					for(final float item : (float[])value) {
						
						list.add(item);
					}
					
					return list;
					
				}else if(type.equals(double.class)) {
					
					final List<Double> list = new ArrayList<>();
					
					//
					for(final double item : (double[])value) {
						
						list.add(item);
					}
					
					return list;
				}
				
			}else if(TypeUtil.isArray(value)) {
				
				return toList((Object[])value);
			}
		}
		
		return null;
	}
	
	/**
	 * Converte o <b>array</b> para {@link List} utilizando o método {@link Arrays#asList(Object...)}<br>
	 * <br>
	 * Obs.: <b>T</b> não pode ser um tipo primitivo.<br>
	 * <br>
	 * Exemplo: Se quiser converter <b>long[]</b> para <b>List<<long>long></b>, então utilize <b>Long[]</b> para obter <b>List<<Long>Long></b>.<br>
	 * 
	 * @param array
	 * @return {@link ArrayList}
	 */
	public static <T> List<T> toList(final T[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			return Arrays.asList(array);
		}
		
		return null;
	}
	
	/**
	 * Converte o <b>list</b> para {@link Vector}
	 * 
	 * @param list
	 * @return {@link Vector}
	 */
	public static <T> Vector<T> toVector(final List<T> list) {
	
		//
		if(CheckUtil.isNotNull(list)) {
			
			return new Vector<T>(Collections.synchronizedList(list));
		}
		
		return null;
	}
	
	/**
	 * Converte o <b>array</b> para {@link Vector}.<br>
	 * <br>
	 * Obs.: <b>T</b> não pode ser um tipo primitivo.<br>
	 * <br>
	 * Exemplo: Se quiser converter <b>long[]</b> para <b>Vector<<long>long></b>, então utilize <b>Long[]</b> para obter <b>Vector<<Long>Long></b>.<br>
	 * 
	 * @param array
	 * @return {@link Vector}
	 */
	public static <T> Vector<T> toVector(final T[] array) {
	
		//
		if(CheckUtil.isNotNull(array)) {
			
			return new Vector<T>(Collections.synchronizedList(toList(array)));
		}
		
		return null;
	}
	
	/**
	 * @param collection
	 * @return {@link Collection} in formatted {@link String}
	 */
	public static String toString(final Collection<?> collection) {
		
		return toString(collection, false);
	}
	
	/**
	 * @param collection
	 * @param defaultFormat
	 * @return {@link Collection} in formatted {@link String}
	 */
	public static String toString(final Collection<?> collection, boolean defaultFormat) {
	
		StringAsync str = new StringAsync();
		
		long count = 0;
		
		// 
		if(CheckUtil.isNotNullAndNotEmpty(collection)) {
			
			// 
			if(defaultFormat) {
				
				return collection.toString();
				
			}else {
				
				Iterator<?> iterator = collection.iterator();
				
				// 
				while(iterator.hasNext()) {
					
					str.appendLine(++count + ": " + iterator.next());
				}
			}
		}
		
		return str.toString();
	}
}
